Práctica 4: Sincronización en el kernel
El objetivo de esta práctica es familiarizarse con las siguientes abstracciones de Linux:
struct kfifo
El módulo de ejemplo refmod.c ilustra la importancia del contador de referencia asociado a un módulo del kernel. Este módulo, al cargarse, expone al usuario un fichero especial de caracteres /dev/refmod. Al leer de dicho fichero especial con cat, el proceso lector se bloquea durante unos segundos, y a continuación retorna devolviendo 0 bytes en la operación de lectura. El periodo de bloqueo durante la lectura –especificado en milisegundos– es configurable, gracias al parámetro stime_ms que exporta el módulo, y que puede establecerse en tiempo de carga en el kernel.
refmod.c
/dev/refmod
cat
stime_ms
Consultar la implementación de la operación de lectura del fichero especial de caracteres (función refmod_read() del módulo). Como puede observarse, el bloqueo se lleva a cabo llamando a la función msleep() vista en clase. Responder de forma razonada a las siguientes preguntas:
refmod_read()
msleep()
cat /proc/refmod
CTRL + C
kill
lsmod
sudo rmmod refmod
Modificar el código de refmod.c para que el contador de referencia del módulo del kernel se incremente al hacer open() en el fichero especial de dispositivo, y se decremente al hacer close() del mismo. Recordad que las funciones que permiten incrementar y decrementar el contador de referencia son try_module_get() y module_put(), respectivamente. Para más información se aconseja consultar cualquiera de las variantes del módulo chardev proporcionadas en la práctica anterior, donde se usan estas funciones. Tras llevar a cabo las modificaciones propuestas, comprobar ahora el valor del contador de referencia del módulo del kernel mientras haya un “cat” en curso sobre el fichero de dispositivo. ¿Es posible descargar ahora el módulo mientras un proceso esté leyendo del fichero especial de dispositivo?
open()
close()
try_module_get()
module_put()
chardev
Probar el funcionamiento del módulo de ejemplo kthread-mod.c , que al cargarse con insmod crea un kernel thread. Este kernel thread pasa casi todo el tiempo bloqueado, y se despierta cada segundo para imprimir un mensaje en el fichero de log del kernel.
kthread-mod.c
insmod
Para poder percibir esta impresión periódica del mensaje (tras cargar el módulo) se recomienda abrir una ventana de terminal adicional y ejecutar el siguiente comando: sudo tail -f /var/log/kern.log. Este comando permite mostrar en tiempo real los mensajes que se van imprimiendo en el fichero de log del kernel:
sudo tail -f /var/log/kern.log
kernel@debian:~$ sudo tail -f /var/log/kern.log [sudo] password for kernel: ... Dec 4 14:15:22 debian kernel: [233644.504010] Tic Dec 4 14:15:23 debian kernel: [233645.524021] Tac Dec 4 14:15:24 debian kernel: [233646.544028] Tic Dec 4 14:15:25 debian kernel: [233647.564029] Tac Dec 4 14:15:26 debian kernel: [233648.584021] Tic Dec 4 14:15:27 debian kernel: [233649.604031] Tac ...
Analizar el código del módulo de ejemplo Clipboard-update que hace uso de wait queues para dotar de semántica bloqueante al dispositivo especial de caracteres que el módulo gestiona. Este módulo de ejemplo es una variante del módulo Clipboard-dev , que se estudió en la práctica anterior.
Clipboard-update
Clipboard-dev
Al cargar el módulo Clibpoard-update en el núcleo, se crea automáticamente el fichero de dispositivo /dev/clipboard_update. A pesar de que la lectura y escritura en este fichero especial de caracteres tiene un comportamiento similar a la entrada /dev/clipboard del módulo en el que se basa (consulta y modificación, respectivamente del “clipboard”) el fichero especial implementado en este caso tiene semántica bloqueante. Todo proceso que lee de la entrada /dev/clipboard_update se queda bloqueado en la operacion clipboard_read() hasta que se produce una actualización (escritura) del contenido del “clipboard”. El proceso escritor, que invoca clipboard_write() es el encargado de despertar a los procesos lectores tras realizar una operación de actualización.
Clibpoard-update
/dev/clipboard_update
/dev/clipboard
clipboard_read()
clipboard_write()
Para probar la funcionalidad de este ejemplo se han de abrir tantas ventanas de terminal como procesos acceden al “clipboard”. A continuación se muestra un ejemplo con cuatro terminales. Para observar los bloqueos se aconseja hacer primero tres lecturas con cat y a continuación la escritura con echo:
echo
Terminal 1
pi@raspberrypi:~ $ cat /dev/clipboard_update new clipboard
Terminal 2
Terminal 3
Terminal 4
pi@raspberrypi:~ $ echo "new clipboard" > /dev/clipboard_update
Tras estudiar el código fuente, se ha de proporcionar una respuesta a las siguientes preguntas:
¿Qué sucede si, mientras un proceso está bloqueado en clipboard_read(), tecleamos CTRL + C en la misma terminal (enviando la señal SIGINT al proceso que ejecuta cat)? ¿Podría conseguirse el mismo comportamiento si se hubiera usado wait_event() en lugar de wait_event_interruptible() en la implementación? Justifica la respuesta.
wait_event()
wait_event_interruptible()
¿Garantiza la implementación exclusión mutua en el acceso a la variable clipboard? En caso de que no sea así propón una solución que garantice que la implementación sea SMP-safe.
Esta práctica consta de tres partes: A, B y C.
Realizar una implementación SMP-safe de la Práctica 1 (módulo modlist) usando spin locks. Para ello se ha de garantizar exclusión mutua entre las distintas regiones de código que acceden a la lista enlazada de enteros (estructura compartida). Asimismo el módulo del kernel debe modificarse para impedir que se lleve a cabo su descarga si el módulo está en uso (p.ej., si uno o varios procesos están accediendo a la entrada /proc/modlist).
modlist
/proc/modlist
Al desarrollar esta práctica ha de tenerse en cuenta que no es posible invocar funciones bloqueantes, como kmalloc(), dentro de spin_lock() y spin_unlock().
kmalloc()
spin_lock()
spin_unlock()
Para verificar la robustez del código desarrollado ha de crearse uno o varios scripts BASH (a lanzar desde varios terminales simultáneamente) que permitan realizar accesos concurrentes a la lista enlazada. Se deja a elección del estudiante determinar el tipo de procesamiento realizado por el script (o scripts) a desarrollar.
Implementar el módulo ProdCons descrito en clase, que gestiona un buffer circular acotado de enteros, y permite a los procesos de usuario insertar y eliminar números en ese buffer realizando escrituras y lecturas en un fichero especial de caracteres (/dev/prodcons). Estas operaciones de lectura (eliminación) y escritura (inserción) han de tener semántica productor consumidor:
ProdCons
/dev/prodcons
$ echo 7 > /dev/prodcons
$ cat /dev/prodcons
read()
Ejemplo de ejecución (se asume que buffer puede alojar como mucho 4 enteros)
kernel@debian:~/ProdCons$ echo 4 > /dev/prodcons kernel@debian:~/ProdCons$ echo 5 > /dev/prodcons kernel@debian:~/ProdCons$ echo 6 > /dev/prodcons kernel@debian:~/ProdCons$ cat /dev/prodcons 4 kernel@debian:~/ProdCons$ cat /dev/prodcons 5 kernel@debian:~/ProdCons$ cat /dev/prodcons 6 kernel@debian:~/ProdCons$ cat /dev/prodcons <<proceso se queda bloqueado>>
Al igual que en el código a desarrollar en la parte A, se debe garantizar que el módulo NO pueda descargarse si algún proceso de usuario está utilizando sus servicios.
El buffer circular de enteros gestionado por el módulo se implementará utilizando la estructura de datos struct kfifo del kernel, que representa un buffer circular de bytes. Para utilizar esta estructura de datos genérica del kernel ha de incluirse el fichero de cabecera <linux/kfifo.h>. Aunque existen varias formas para inicializar la estructura, se recomienda definir una variable global del tipo adecuado:
<linux/kfifo.h>
struct kfifo nombre_variable;
y a continuación inicializar la estructura con kfifo_alloc(). La memoria debe liberarse con kfifo_free() preferentemente en la función de cleanup del módulo.
kfifo_alloc()
kfifo_free()
En la práctica se reservará espacio para alojar hasta 4 u 8 enteros en el buffer. El tamaño máximo del buffer circular se podrá establecer mediante un parámetro configurable del módulo, a fijar en tiempo de carga.
Como un entero ocupa 4 bytes, las inserciones y eliminaciones de números se realizarán mediante las operaciones de inserción y eliminación múltiple de esta estructura de datos: kfifo_in() y kfifo_out().
kfifo_in()
kfifo_out()
Ejemplo inserción y eliminación (extración) de entero en struct kfifo
#include <linux/kfifo.h> void insertar_entero(struct kfifo* cbuf, int n){ kfifo_in(cbuf,&n,sizeof(int)); } int extraer_entero(struct kfifo* cbuf, int n){ int n; kfifo_out(cbuf,&n,sizeof(int)); return n; }
Breve descripción de operaciones de struct kfifo (pk denota un parámetro puntero a struct kfifo)
pk
kfifo_alloc(pk,size,mask)
size
mask
GFP_KERNEL
kfifo_free(pk)
kfifo_len(pk)
kfifo_avail(pk)
kfifo_size(pk)
kfifo_is_full(pk)
kfifo_is_empty(pk)
kfifo_in(pk,from,n)
n
void* from
kfifo_out(pk,to,n)
void* to
kfifo_reset(pk)
Para implementar las operaciones de bloqueo en esta práctica se emplearán tres semáforos del kernel:
mtx
huecos
elementos
Crear una variante SMP-safe y thread-safe del driver desarrollado en la práctica 3 ParteC – driver que gestiona el display 7 segmentos de la placa Bee v2.0. Para ello se creará una variante de dicho driver que asegure lo siguiente:
/dev/display7s
release()
-EBUSY
write()
display7s_write()
Crear una variante del ejemplo Clipboard-update donde se utilicen semáforos del kernel en lugar de wait queues para dotar de semántica bloqueante a la operación de lectura en /dev/clipboard_update. Pista: Se aconseja la utilización de dos semáforos, uno de ellos a utilizar como cerrojo (contador inicializado a 1), y el otro como cola de espera (contador inicializado a 0) para bloquear a los procesos que lean de /dev/clipboard_update. Asimismo debe mantenerse un contador global para llevar la cuenta de los procesos esperando en el segundo semáforo.
La práctica ha de entregarse a través del Campus Virtual en un fichero comprimido (.tar.gz o .zip) con la siguiente estructura de directorios:
Plazo de entrega: Hasta el 17 de noviembre.
Es obligatorio mostrar el funcionamiento de la práctica en clase.